﻿using HslCommunication;
using HslCommunication.Profinet.Siemens;
using Microsoft.AspNetCore.SignalR;
using Testudo.Core;
using Testudo.Models;
using Testudo.Services;
using Testudo.WebApp.Events;
using Testudo.WebApp.Hubs;

namespace Testudo.WebApp.Services;

internal class ScenarioProxy : IScenarioProxy
{
    private IHubContext<WcsProxyHub> HubContext { get; }
    public Guid Id => Config?.Id ?? Guid.Empty;

    private ScenarioConfig Config { get; set; }
    private List<LineProxy> Lines { get; set; } = [];
    private List<SrmProxy> Srms { get; set; } = [];
    public ScenarioProxy(IHubContext<WcsProxyHub> hubContext)
    {
        HubContext = hubContext;
    }

    async Task IScenarioProxy.Load(ScenarioConfig config)
    {
        Config = config;

        Lines = Config.Lines.Select(line => new LineProxy(line, HubContext)).ToList();
        Srms = Config.Srms.Select(srm => new SrmProxy(srm, HubContext)).ToList();
    }

    async Task IScenarioProxy.Unload()
    {
        await Stop();
        Lines = [];
        Srms = [];
        Config = null;
    }

    public async Task Start()
    {
        foreach (var item in Lines)
        {
            await item.Start();
        }
        foreach (var item in Srms)
        {
            await item.Start();
        }
    }

    public async Task Stop()
    {
        foreach (var item in Lines)
        {
            await item.Stop();
        }
        foreach (var item in Srms)
        {
            await item.Stop();
        }
    }
}


internal abstract class ProxyBase
{
    protected CancellationTokenSource tokenSource;
    public virtual async Task Start()
    {
        tokenSource = new CancellationTokenSource();
    }

}

internal class LineProxy
{
    private LineConfig Config { get; }
    private List<SiteProxy> Sites { get; set; }
    private SiemensS7Net Plc { get; set; }

    public LineProxy(LineConfig config, IHubContext<WcsProxyHub> hubContext)
    {
        Config = config;
        Plc = new SiemensS7Net(SiemensPLCS.S1500)
        {
            IpAddress = config.Plc.Ip,
            Port = Config.Plc.Port,
            ConnectTimeOut = 10000,
            ReceiveTimeOut = 5000,
            Rack = 0,
            Slot = 0,
        };

        Sites = Config.Sites.Select(site => new SiteProxy(hubContext, site, Plc, config.Plc.Interval)).ToList();
    }

    public async Task Start()
    {
        //await Plc.ReadAsync<SiteData>();
        foreach (var item in Sites)
        {
            await item.Start();
        }
    }

    public async Task Stop()
    {
        foreach (var item in Sites)
        {
            await item.Stop();
        }
    }
}

internal class SiteProxy
{
    private CancellationTokenSource _tokenSource;
    private IHubContext<WcsProxyHub> HubContext { get; }
    private SiteNodeConfig Config { get; }
    private SiemensS7Net Plc { get; }
    private int Interval { get; }

    public SiteProxy(IHubContext<WcsProxyHub> hubContext, SiteNodeConfig config, SiemensS7Net plc, int interval)
    {
        HubContext = hubContext;
        Config = config;
        Plc = plc;
        Interval = interval;
    }

    public async Task Start()
    {
        _tokenSource = new CancellationTokenSource();

        await Task.Run(async () =>
        {
            do
            {
                var plc = new SiemensS7Net(SiemensPLCS.S1500);
                plc.IpAddress = "192.168.1.208";
                plc.Port = 102;
                plc.ConnectTimeOut = 10000; // 连接超时，单位毫秒
                plc.ReceiveTimeOut = 5000; // 接收超时，单位毫秒
                plc.Rack = 0;
                plc.Slot = 0;


                var result = await plc.ReadInt16Async("DB1.860");

                var values = Plc.ReadFieldsValue(Config.Fields);
                var notifyData = new NotifyFieldUpdated(targetId: Config.Id, values: values);

                await HubContext.Clients.All.SendAsync("Notify", notifyData, _tokenSource.Token);
                await Task.Delay(Interval, _tokenSource.Token);
            } while (!_tokenSource.IsCancellationRequested);
        }, _tokenSource.Token);
    }

    public async Task Stop()
    {
        if (!_tokenSource?.IsCancellationRequested == true)
        {
            await _tokenSource.CancelAsync();
        }
    }
}

internal class SrmProxy
{
    private CancellationTokenSource _tokenSource;

    private SrmConfig Config { get; }
    private IHubContext<WcsProxyHub> HubContext { get; }
    private SiemensS7Net Plc { get; set; }

    public SrmProxy(SrmConfig config, IHubContext<WcsProxyHub> hubContext)
    {
        Config = config;
        HubContext = hubContext;
        Plc = new SiemensS7Net(SiemensPLCS.S1500)
        {
            IpAddress = config.Plc.Ip,
            Port = Config.Plc.Port,
            ConnectTimeOut = 10000,
            ReceiveTimeOut = 5000,
            Rack = 0,
            Slot = 0,
        };
    }


    public async Task Start()
    {
        _tokenSource = new CancellationTokenSource();
        await Task.Run(async () =>
        {
            do
            {
                var values = Plc.ReadFieldsValue(Config.Fields);
                var notifyData = new NotifyFieldUpdated(targetId: Config.Id, values: values);

                await HubContext.Clients.All.SendAsync("Notify", notifyData, _tokenSource.Token);
                await Task.Delay(Config.Plc.Interval, _tokenSource.Token);
            } while (!_tokenSource.IsCancellationRequested);
        }, _tokenSource.Token);
    }

    public async Task Stop()
    {
        if (!_tokenSource?.IsCancellationRequested == true)
        {
            await _tokenSource.CancelAsync();
        }
    }
}

internal static class NetworkDeviceBaseExtensions
{
    public static Dictionary<string, object> ReadFieldsValue(this SiemensS7Net target, IEnumerable<PlcDataField> fields)
    {
        var result = new Dictionary<string, object>();

        foreach (var field in fields)
        {
            result[field.Name] = target.ReadFieldValue(field);
        }
        return result;
    }

    public static object ReadFieldValue(this SiemensS7Net target, PlcDataField field)
    {
        object GetValue<T>(OperateResult result)
        {
            if (result is OperateResult<T> operateResult)
            {
                return result.IsSuccess ? operateResult.Content : default;
            }
            return default;
        }

        Dictionary<FieldType, (Func<string, OperateResult>, Func<OperateResult, object>)> calcDic = new()
        {
            [FieldType.Bool] = (target.ReadBool, GetValue<bool>),
            [FieldType.Int16] = (target.ReadInt16, GetValue<short>),
            [FieldType.UInt16] = (target.ReadUInt16, GetValue<ushort>),
            [FieldType.Int32] = (target.ReadInt32, GetValue<int>),
            [FieldType.UInt32] = (target.ReadUInt32, GetValue<uint>),
            [FieldType.Long] = (target.ReadInt64, GetValue<long>),
            [FieldType.ULong] = (target.ReadUInt64, GetValue<ulong>),
            [FieldType.Float] = (target.ReadFloat, GetValue<float>),
            [FieldType.Double] = (target.ReadDouble, GetValue<double>),
            [FieldType.String] = ((address) => target.ReadString(address, 0), GetValue<string>),
        };

        try
        {
            var tmp = target.ReadInt16(field.Address);
            var (read, getValue) = calcDic[field.Type];
            return getValue(read(field.Address));
        }
        catch (Exception)
        {
            return default;
        }
    }
}